package chiffrage;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.Security;
import java.security.spec.AlgorithmParameterSpec;
import java.util.HashMap;
import java.util.HashSet;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.RC5ParameterSpec;
import javax.crypto.spec.SecretKeySpec;

import org.bouncycastle.jce.provider.BouncyCastleProvider;

public class DecryptFile {
	private File fs, fd, fc;
	byte[] cle;
	double tm;
	private String algo, mode = "", remb = "";

	public static enum Mode {
		ECB, CBC, CFB, CTR, CTS, OFB, PCBC
	};

	public static enum Remb {
		NoPadding, PKCS5Padding, ISO10126Padding
	};

	public static enum Algo {
		AES, BLOWFISH, DES, DESede, RC2, RC4, RC5, Salsa20, HC128
	};

	public HashMap<String, Integer> tailleCle;
	DataInputStream di;
	BufferedOutputStream bo;
	DataInputStream bis;
	Cipher cf;

	public DecryptFile(File fs, File fd, Algo a, Mode m, Remb r)
			throws FileNotFoundException {
		this.fs = fs;
		this.fd = fd;
		this.algo = a.name();
		this.mode = m.name();
		this.remb = r.name();

		this.initailleCle();

	}

	// pour faire un decryptage par bloc
	public DecryptFile(File fs, File fd, Algo a) throws FileNotFoundException {
		this.fs = fs;
		this.fd = fd;
		this.algo = a.name();
		this.initailleCle();
		
	}

	private void initailleCle() {
		tailleCle = new HashMap<String, Integer>();
		tailleCle.put(Algo.AES.name(), 128);
		tailleCle.put(Algo.BLOWFISH.name(), 32);
		tailleCle.put(Algo.DES.name(), 56);
		tailleCle.put(Algo.DESede.name(), 168);
		tailleCle.put(Algo.RC2.name(), 120);
		tailleCle.put(Algo.RC4.name(), 48);
		tailleCle.put(Algo.RC5.name(), 1024);
	}

	public SecretKeySpec readCle() throws IOException {
		this.bis = new DataInputStream(new FileInputStream(new File(
				fs.getParent()
						+ "\\"
						+ "CLE_"
						+ this.fs.getName().substring(
								this.fs.getName().indexOf("_") + 1))));
		// this.bis = new DataInputStream(in) (new FileInputStream(new
		// File(fs.getParent() + "\\" + "CLE_" +
		// this.fs.getName().substring(test.indexOf("_")+1)));
		int i = this.bis.readInt();
		byte[] buff = new byte[i];
		this.bis.read(buff);
		return new SecretKeySpec(buff, this.algo);
	}

	public IvParameterSpec readNonce(byte[] nonce) throws IOException {
		this.di.read(nonce);
		return new IvParameterSpec(nonce);
	}

	public void Decrypt() throws InvalidKeyException,
			InvalidAlgorithmParameterException, NoSuchAlgorithmException,
			IOException, NoSuchPaddingException, IllegalBlockSizeException,
			BadPaddingException {
		try {
			this.bo = new BufferedOutputStream(new FileOutputStream(this.fd));
			this.di = new DataInputStream(new FileInputStream(this.fs));
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		Security.addProvider(new BouncyCastleProvider());

		if (this.algo.equals(DecryptFile.Algo.Salsa20.name())
				|| this.algo.equals(DecryptFile.Algo.HC128.name())) {
			byte[] nonce = new byte[(this.algo.equals(DecryptFile.Algo.Salsa20
					.name())) ? 8 : 16];
			SecretKey sKey = this.readCle();
			IvParameterSpec iv = this.readNonce(nonce);
			Cipher cf = Cipher.getInstance(this.algo);
			cf.init(Cipher.DECRYPT_MODE, sKey, iv);
			byte[] buffer = new byte[1024];
			int nl;
			while ((nl = di.read(buffer)) != -1) {
				bo.write(cf.update(buffer, 0, nl));
			}
			bo.write(cf.doFinal());
		} else if (mode == "") {
			try {
				cf = Cipher.getInstance(this.algo);

				System.out.println(this.algo);
				SecretKey skey = this.readCle();
				cf.init(Cipher.DECRYPT_MODE, skey);
				byte[] buffer = new byte[1024];
				int nl;
				while ((nl=di.read(buffer) )!= -1) {
					bo.write(cf.update(buffer,0,nl));
				}
				bo.write(cf.doFinal());
			} catch (NoSuchAlgorithmException | NoSuchPaddingException
					| InvalidKeyException | IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		} else

		if (mode.equals(DecryptFile.Mode.CBC.name())) {
			byte[] nonce;
			if (this.algo.equals(DecryptFile.Algo.RC5.name())) {
				nonce = new byte[16];
				Security.addProvider(new BouncyCastleProvider());
				SecretKeySpec skey = this.readCle();
				AlgorithmParameterSpec rcSpec = new RC5ParameterSpec(2, 12, 32,
						this.readNonce(nonce).getIV());
				cf = Cipher.getInstance(this.algo + "/" + this.mode + "/"
						+ this.remb);
				cf.init(Cipher.DECRYPT_MODE, skey, rcSpec);

			} else {
			
				nonce = new byte[(this.algo.equals(DecryptFile.Algo.AES.name()) ? 16
						: 8)];
				cf = Cipher.getInstance(this.algo + "/" + this.mode + "/"
						+ this.remb);
				cf.init(Cipher.DECRYPT_MODE, this.readCle(), this.readNonce(nonce));
			}
			try {;
				byte[] buffer = new byte[1024];
                int nl;
                while ((nl=di.read(buffer) )!= -1) {
					bo.write(cf.update(buffer,0,nl));
				}
				bo.write(cf.doFinal());
			} catch (IllegalBlockSizeException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (BadPaddingException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		} else /*
				 * if (this.mode.equals(FileCrypter.Mode.CFB.name()) ||
				 * this.mode.equals(FileCrypter.Mode.OFB.name()) ||
				 * this.mode.equals(FileCrypter.Mode.PCBC.name()))
				 */{
			byte[] nonce;
			if (this.algo.equals(DecryptFile.Algo.RC5.name())) {
				nonce = new byte[16];
				Security.addProvider(new BouncyCastleProvider());
				// SecretKeySpec skey = new SecretKeySpec(km, "RC5");
				SecretKeySpec skey = this.readCle();
				AlgorithmParameterSpec rcSpec = new RC5ParameterSpec(2, 12, 32,
					this.readNonce(nonce).getIV());
				cf = Cipher.getInstance(this.algo + "/" + this.mode + "/"
						+ this.remb);
				cf.init(Cipher.DECRYPT_MODE, skey, rcSpec);
			} else {
				SecretKey skey = this.readCle();
				nonce = new byte[(this.algo.equals(DecryptFile.Algo.AES.name()) ? 16
						: 8)];
				IvParameterSpec iv = this.readNonce(nonce);
				cf = Cipher.getInstance(this.algo + "/" + this.mode + "/"
						+ this.remb);
				cf.init(Cipher.DECRYPT_MODE, skey, iv);
			}
			int nl = 0;
			try {
				byte[] buffer = new byte[1024];
				while ((nl = di.read(buffer)) != -1) {
					bo.write(cf.update(buffer, 0, nl));
				}
				bo.write(cf.doFinal());

			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (IllegalBlockSizeException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (BadPaddingException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}

		try {
			this.di.close();
			this.bo.close();
			this.bis.close();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

}